package model

import (
	"fmt"
	"inventory/api/inventory"
	"inventory/internal/constant"
	"inventory/pkg/ecode"
	"inventory/pkg/util"
	"sort"
	"strings"
	"time"
)

/* 中间模型，用于解构冻结单并继续后续处理 */

// FreezeProcessDetail
type FreezeProcessDetail struct {
	StorageKey        string
	StorageType       uint8
	FreezeOrderNumber string
	Quantity          float64
	NeedCreate        bool
}

// ActivityDetail
type ActivityDetail struct {
	ID                uint64
	StorageKey        string
	StorageType       uint8
	AvailableQuantity float64
	// key为相对数量
	Subs map[float64]*ActivityDetail
}

// ActivityOrder
type ActivityOrder struct {
	*FreezeOrder
	// 这里仓库ID + StorageKey联合作为key
	DetailMap map[string]*ActivityDetail
}

// 解构冻结请求
type FreezeProcessRequest struct {
	SgId        uint32
	RequestNum  string
	Source      inventory.SourceEnum
	ExpireTime  util.NullTime
	DetailCount int
	// 仓库map
	WarehouseMap WarehouseMap
	// 解构明细map：仓库ID => 仓储对象
	DetailMap map[uint32]map[string]*FreezeProcessDetail
	// 明细名称
	DetailNameMap map[string]string
	// DirectBomMap
	DirectBomMap map[string]map[string]*BomConfig
	// 反向bomMap，用于基于不存在的物料查询下单仓储对象名称
	ReverseBomMap map[string]util.StringBoolMap
	// (无冻结单)仓储对象序列，用于加载bom
	DirectStorageSet util.StringBoolMap
	// (无冻结单)库存计算key序列: 仓库ID => 库存对象key序列（物料解构）
	DirectWarehouseQueryMap map[uint32]map[string]float64
	// 待推送仓库库存变集合
	AffectWarehouseSet util.UInt32BoolMap
	// 冻结单关联仓储对象序列
	RefFreezeKeys map[string][]string
	// 冻结单号 => 冻结key => 冻结单明细
	RefFreezeMap map[string]*ActivityOrder
	// 缺货集合
	lackDetailNames util.StringBoolMap
}

// 校验并录入冻结单本体
func NewFreezeProcessRequest(sgId uint32, requestNum string, source inventory.SourceEnum, expireTime string) (
	req *FreezeProcessRequest, err error) {
	req = &FreezeProcessRequest{
		SgId:                    sgId,
		RequestNum:              requestNum,
		Source:                  source,
		WarehouseMap:            make(WarehouseMap),
		DetailMap:               make(map[uint32]map[string]*FreezeProcessDetail),
		DetailNameMap:           make(map[string]string),
		DirectBomMap:            make(map[string]map[string]*BomConfig),
		ReverseBomMap:           make(map[string]util.StringBoolMap),
		DirectStorageSet:        make(util.StringBoolMap),
		DirectWarehouseQueryMap: make(map[uint32]map[string]float64),
		AffectWarehouseSet:      make(util.UInt32BoolMap),
		RefFreezeMap:            make(map[string]*ActivityOrder),
		RefFreezeKeys:           make(map[string][]string),
		lackDetailNames:         make(util.StringBoolMap),
	}
	switch req.Source {
	case inventory.SourceEnum_MALL_ORDER, inventory.SourceEnum_XT_ORDER, inventory.SourceEnum_XT_ACTIVITY:
		break
	default:
		err = ecode.BadRequest("冻结单类型不合法")
		return
	}
	if err = req.verifyAndSaveExpireTime(expireTime); err != nil {
		return
	}
	return
}

// 获取仓库序列
func (f *FreezeProcessRequest) GetWarehouseKeys() (keys []uint32) {
	keys = make([]uint32, 0, len(f.DetailMap))
	for key := range f.DetailMap {
		keys = append(keys, key)
	}
	return
}

// 获取活动单号序列
func (f *FreezeProcessRequest) GetFreezeOrderNumbers() (keys []string) {
	keys = make([]string, 0, len(f.RefFreezeKeys))
	for key := range f.RefFreezeKeys {
		keys = append(keys, key)
	}
	// 按顺序排序，防止交叉死锁
	sort.Strings(keys)
	return
}

func (f *FreezeProcessRequest) verifyAndSaveExpireTime(reqExpireTime string) (err error) {
	if reqExpireTime != "" {
		var rawExpireTime time.Time
		if rawExpireTime, err = util.ParseTime(reqExpireTime); err != nil {
			err = ecode.BadRequest("ExpireTime:" + reqExpireTime)
			return
		}
		if rawExpireTime.Before(time.Now()) {
			err = ecode.BadRequest("过期时间不能小于当前时间")
			return
		}
		f.ExpireTime = util.NullTime{
			Time:  rawExpireTime,
			Valid: true,
		}
	} else {
		f.ExpireTime = util.NullTime{}
	}
	return
}

// GetDetailName
func (f *FreezeProcessRequest) AppendLackDetail(storageKey string) {
	if detailName, hasDetail := f.DetailNameMap[storageKey]; hasDetail {
		f.lackDetailNames[detailName] = true
	} else {
		for parentName := range f.ReverseBomMap[storageKey] {
			f.lackDetailNames[parentName] = true
		}
	}
	return
}

// CheckLackDetails
func (f *FreezeProcessRequest) CheckLackDetails() error {
	if len(f.lackDetailNames) != 0 {
		return ecode.AppendMessage(constant.ErrStorageUnavailable,
			fmt.Sprintf("商品%s库存不足", strings.Join(f.lackDetailNames.GetKeys(), ",")))
	}
	return nil
}
